package com.xux.dao.support.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import com.xux.dao.support.DaoSupport;
import com.xux.model.po.OrderSlic;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.GroupOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * MongoDB通用Dao抽象实现.
 *
 * @param <T> 集合类型
 * @author LaoWang
 * @date 2020-10-20
 */
@Slf4j
public abstract class AbstractDaoSupport<T> implements DaoSupport<T> {

    private static final String ID_KEY = "_id";

    @Autowired
    @Qualifier("mongoTemplate")
    protected MongoTemplate mongoTemplate;

    private final Class<T> clazz;

    @SuppressWarnings("unchecked")
    public AbstractDaoSupport() {
        this.clazz = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
    }

    /**
     * 保存一个对象到mongodb.
     *
     * @param bean 对象
     * @return
     */
    @Override
    public T save(final T bean) {
        mongoTemplate.save(bean);
        return bean;
    }

    /**
     * 根据id删除对象.
     *
     * @param id id
     */
    @Override
    public void deleteById(final Object id) {
        Query query = new Query();
        query.addCriteria(Criteria.where(ID_KEY).is(id));
        mongoTemplate.remove(query, this.getEntityClass());
    }


    /**
     * 通过条件查询实体(集合).
     *
     * @param query 条件对象
     * @return
     */
    @Override
    public List<T> find(final Query query) {
        return mongoTemplate.find(query, this.getEntityClass());
    }

    /**
     * 根据条件查询id集合.
     *
     * @param criteria
     * @return
     */
    @SuppressWarnings("unchecked")
    public <E> List<E> findIds(final Criteria criteria, Class<E> e) {
        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.project(ID_KEY)
        );
        AggregationResults<Document> aggregate = mongoTemplate.aggregate(aggregation, getEntityClass(), Document.class);
        List<Document> mappedResults = aggregate.getMappedResults();
        return mappedResults.stream().map(map -> (E) map.get(ID_KEY)).collect(Collectors.toList());
    }

    /**
     * 通过一定的条件查询一个实体.
     *
     * @param query 条件对象
     * @return
     */
    @Override
    public T findOne(final Query query) {
        return mongoTemplate.findOne(query, this.getEntityClass());
    }

    /**
     * 查询一个字段去重后的的列表，适合用于返回筛选的数据.
     *
     * @param query       条件对象
     * @param field       查询的字段
     * @param resultClass 返回类型
     * @param <E>         返回类型
     * @return 查询结果
     */
    public <E> List<E> findDistinct(final Query query, final String field, final Class<E> resultClass) {
        return mongoTemplate.findDistinct(query, field, getEntityClass(), resultClass);
    }

    /**
     * 通过条件查询更新数据.
     *
     * @param query  条件对象
     * @param update 更新
     */
    @Override
    public void update(final Query query, final Update update) {
        mongoTemplate.updateMulti(query, update, this.getEntityClass());
    }

    /**
     * 通过ID获取记录.
     *
     * @param id id
     * @return
     */
    @Override
    public T findById(final Object id) {
        return mongoTemplate.findById(id, this.getEntityClass());
    }


    /**
     * 求数据总和.
     *
     * @param query 条件对象
     * @return
     */
    @Override
    public long count(final Query query) {
        return mongoTemplate.count(query, this.getEntityClass());
    }

    /**
     * 获取需要操作的实体类class.
     *
     * @return
     */
    protected Class<T> getEntityClass() {
        return this.clazz;
    }

    /**
     * 分组并求和统计.
     *
     * @param criteria           查询条件
     * @param groupFields     分组字段
     * @param sumField        求和统计字段
     * @param sumResultAttr   求和统计后的结果存放的属性名
     * @param countResultAttr 计数统计后的属性名
     * @param clazz           返回类型
     * @param <E>             返回类型
     * @return 统计结果
     */
    public <E> List<E> group(final Criteria criteria, final String sumField, final String sumResultAttr, final String countResultAttr, final Class<E> clazz, final String... groupFields) {
        List<AggregationOperation> operations = new ArrayList<>();
        //查询字段 = 查询条件 + 分组字段 + 统计字段
        Set<String> querySet = CollUtil.newHashSet(criteria.getCriteriaObject().keySet());
        querySet.addAll(Arrays.asList(groupFields));
        if (Objects.nonNull(sumField)) {
            querySet.add(sumField);
        }
        // 先筛选数据
        operations.add(Aggregation.match(criteria));
        // 查询的字段
        operations.add(Aggregation.project(ArrayUtil.toArray(querySet, String.class)));
        // 再做分组统计
        GroupOperation groupOperation = Aggregation.group(groupFields);
        for (String groupField : groupFields) {
            groupOperation = groupOperation.first(groupField).as(groupField);
        }
        // 进行求和统计
        if (Objects.nonNull(sumField) && Objects.nonNull(sumResultAttr)) {
            groupOperation = groupOperation.sum(sumField).as(sumResultAttr);
        }
        // 进行计数统计
        if (Objects.nonNull(countResultAttr)) {
            groupOperation = groupOperation.count().as(countResultAttr);
        }
        operations.add(groupOperation);
        Aggregation aggregation = Aggregation.newAggregation(operations);
        AggregationResults<E> results = mongoTemplate.aggregate(aggregation, OrderSlic.class, clazz);
        return results.getMappedResults();
    }

}
